//六、this

/*
* 执行上下文，都有三个重要属性：
* 变量对象(Variable object，VO)
* 作用域链(Scope chain)
* this
* */

//this的指向问题: this指向函数执行时的上下文对象
/*
* 1.this的值在函数调用时确定,而不是函数定义时
* 2.this的指向取决于函数的调用方式
* 3.严格模式和非严格模式下this的行为不同
* */

//1.全局上下文的this
//浏览器环境
console.log(this); //window对象
var a = 1;
console.log(this.a); // 1 (全局变量是 window 的属性)

//Node.js 环境
console.log(this); //{}  (空对象，不是global)
console.log(globalThis); //全局对象

//2.函数调用的this
//普通函数调用:
//非严格模式:
function showThis(){
    console.log(this);
}
showThis();
//严格模式:
"use strict";
function showThis(){
    console.log(this);
}
showThis();//undefined

//3.对象方法的调用
var obj = {
    name:"Alice",
    sayName:function (){
        console.log(this.name);
    }
};
obj.sayName(); // ”Alice“  - this 指向 obj

//4.构造函数的this
function Person(name){
    this.name = name;
    console.log(this); //Person (name: "Bob")
}
var person = new Person("Bob");
console.log(person.name); // "Bob"

// new 操作符的执行过程
/*
* 1.创建一个新对象
* 2.将this绑定到这个新对象
* 3.执行构造函数代码
* 4.返回这个新对象
* */

//5.显式绑定this

// call / apply

function introduce(greeting,punctuation){
    console.log(greeting + ", I、m" + this.name + punctuation);
}
var person1 = { name:"Alice"};
var person2 = { name:"Bob"};

introduce.call(person1,"hello","!") //hello,I、m Alice !
introduce.apply(person2,["Hi","!!"])  // hello,I、m Bob !!

//bind

function sayAge(){
    console.log(this.age);
}
var person = {age:25};
var boundFunc = sayAge.bind(person);
boundFunc(); // 25 ===》 this 永久绑定到peson


//6.箭头函数中的this
//箭头函数没有自己的this,继承外层作用域的this

var obj = {
    name:"Alice",
    regular:function (){
        console.log("Regular:",this.name);//Aclie
        setTimeout(function () {
            console.log('Timeout regular:',this.name); //"" window.name setTimeout为全局内置函数 this指向window
        },1000)
    },
    arrow:function (){
        console.log('Arrow outer:',this.name);//Aclie
        setTimeout(()=>{
            console.log('Timeout arrow:',this.name)  // setTimeout内的箭头函数没有自己的this，指向外部作用域arrow的this

        },100)
    }

}
obj.regular();
obj.arrow();

//7.事件处理函数中的this

// DOM事件
//普通函数的情况
let button;
button.addEventListener('click',function (){
    console.log(this);  //this指向button 元素
})
//箭头函数的情况
button.addEventListener('click',()=>{
    console.log(this); //指向外层this,一般来说是window
})

//内联事件
// <button onclick="console.log(this)" >Click</button>
// this指向button元素


//8.类中的this
//class中的方法默认使用严格方法

class Person {
     constructor(name) {
         this.name = name;
     }
     sayName(){
         console.log(this.name)
     }
     delayedSayName(){
         setTimeout(function (){
             console.log(this.name); //默认使用颜色模式，该this指向undefined，错误调用
         },100);
         setTimeout(()=>{
             console.log(this.name);  //箭头函数没有this，this指向外部作用域的this,正常调用
         },100)
     }
}

const person = new Person("Charlie");
person.sayName(); //Charlie

//9.this的优先级
//从高到低
//1.new 绑定： new fOO()
//2.显式绑定 call/apply/bind
//3.隐式绑定 obj.foo()
//4.默认绑定 foo()

function test(){
    console.log(this.name);
}
var obj1 = { name:"obj1",test:test};
var obj2 = { name:"obj2",test:test};
//优先级演示
var bound = test.bind(obj1);
bound.call(obj2);  // obj1 bind的优先级高于 call

//10.特殊情况下的this
//数组方法回调:
var obj = {
    values:[1,2,3],
    print:function (){
        this.values.forEach(function (item){
            console.log(item,this);//this 指向window/undefined
        })
        this.values.forEach(function (item){
            console.log(item,this);//this 指向obj
        },this); //传递thisArg
    }
}
//间接引用
var obj = {
    name:"Alice",
    sayName:function (){
        console.log(this.name);
    }
};
var say = obj.sayName;
say();  //""  丢失this 绑定

(obj.sayName()); //Alice
(false ||obj.sayName)(); // ""  间接引用丢失this

//11.确认this指向的步骤

/*
* 1.判断是否使用new调用===>指向新对象 (构造函数)
* 2.判断是否使用call/apply/bind ===》 指向指定对象
* 3.判断是否作为对象方法调用===>指向该对象
* 4.判断是否为箭头函数===>继承外层this
* 5.默认情况：
*   严格模式: undefined
*   非严格模式:全局对象
* */

var name = 'Global';
var obj = {
    name:'Object',
    regular:function (){
        console.log(this.name);
        function inner(){
            console.log(this.name);
        }
        inner();
        var arrow =()=>{
            console.log(this.name);
        };
        arrow();
    },
    arrowL:()=>{
        console.log(this.name);
    }
}

obj.regular();
/*
*  Object  regular由obj调用,this指向obj
*  Global  普通函数调用 this指向全局 window
*  Object  箭头函数,this指向外部作用域的obj
* */
obj.arrow();
// Global  箭头函数，this指向外部全局作用域window

//12.总结
/*
*1.调用方式决定: this在调用时确定，与定位位置无关
*2.四种绑定模式:
* 默认绑定模式: 全局对象/undefined
* 隐式绑定：调用上下文对象
* 显式绑定:call/apply/bind
* new绑定:新创建对象
* 3.箭头函数：继承外层this,无法更改
* 4.严格模式影响:默认绑定为undefined
* */

/*
* 普通模式看调用
* 方法调用看点前(obj.foo()看obj)
* 构造函数看new
* 箭头函数看外层
* 显式绑定看参数
* */


// 从ECMAScript规范解读this

//ECMAScript: 语言类型和规范类型
//语言类型: 开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的Undefined, Null, Boolean, String, Number, 和 Object
//规范类型: 规范类型相当于 meta-values，是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。
//规范类型包括：Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record

//1.核心概念 --- Reference类型
// 只存在于规范里的抽象类型，用来解释诸如delete,typeof以及赋值等操作行为，并不存在于实际的js代码中
//Reference类型组成:
/*
* 1.base value:  引用所基于的对象,例如：undefined,Object,String,Number,Boolean,Environment Record等
* 2.referenced name:  一个字符串，表示属性名
* 3.strict reference flag:  一个布尔值,表示是否在严格模式下
* */

//示例：obj.method
/*
* base value 是 obj
* referenced name 是method
* strict reference flag取决于代码是否在严格模式下
* this的值
* */

//2.确定this的算法
// 函数调用,执行EvaluateCall
/*
* 1.使用EvaluateDirectCall或类似逻辑
* 2.获取函数调用时左侧表达式计算结果的Reference Record。
* 3.调用抽象操作GetThisValue
* */
/*
* GetThisValue(V)规则:
* 如果V是一个Reference Record，返回V.[[Base]]
* 否则抛出错误
* */
//this 是函数调用时左侧引用(Reference)的base value.

//3.不同调用方式的this规范解读

//3.1 方法调用 obj.method();
const obj = {
    method(){
        console.log(this)
    }
}
obj.method();
/*
* 1.计算成员访问obj.method
*   它产生一个Reference
*    base value = obj
*    referenced name = "method"
* 2.执行函数调用obj.method()
* 3.GetThisValue被调用,传入上面的Reference
* 4.this被设置为base value，即obj
* */

//3.2 正常函数调用
function foo(){
    console.log(this);  //非严格模式下this指向全局对象 (如window),严格模式下this指向undefined
};
foo();

//1.计算标识符foo
/*
*  它产生一个Reference
*  在全局作用域中，base value是全局环境记录(Global Environment Record),这不是一个对象
* */
//2.执行函数调用foo()
//3.GetThisValue被调用，传入上面的Reference
//4.当一个Reference的base value是环境记录时，GetThisValue会调用该环境记录的WithBaseObject()，对于全局环境记录 WithBaseObject()返回undefined
//5.因为base value是全局环境记录且返回了 undefined，次数规范有一个特殊的默认绑定规则来说生效:
/*
*在严格模式下,this保持为undefined
* 在非严格模式下，this被设置为全局对象
* */

//3.3 显式绑定
// call apply bind
function foo1(){
    console.log(this)
}
let obj1 = { x: 1}

foo1.call(obj);
//Function.prototype.call的内部实现call会执行一个名为OrdinaryCallBindThis的操作
/*
* 如果函数使用了严格模式，或者本次调用是尾调用(tall call),那么将this参数直接赋给函数环境的ThisBinding.
*非严格模式: 如果传入的this是null或者undefined,则将全局对象赋给ThisBinding;如果不是,则将传入的值转换为对象后赋给ThisBinging.
*call apply bind 直接绕过了基于Reference的默认规则，显式的指定了this绑定
* */

//3.4 构造函数
function foo2(){
    this.property = 'value';
}

const instance = new foo2(); //this指向新创建的对象 instance
//使用new运算符会执行抽象操作Construct
/*
* 创建一个新的空对象，这个对象的[[Prototype]]被设置为构造函数的prototype属性
* 调用构造函数的[[call]]内部方法，将this绑定设置为这个新创建的对象
* new 操作符拥有最高的优先级，它创建了一个新对象并强制将其作为函数执行的this
* */

//3.5 箭头函数
const obj = {
    method:()=>{
        console.log(this);
    }
}
obj.method(); //this不是obj,而是定义时外层作用域的this

//箭头函数在规范中被称为不定义this绑定的函数
/*
* 当调用箭头函数时，抽象操作OrdinaryCallBindTgus会被跳过，箭头函数内部根本没有自己的this环境
* 当箭头函数内部访问this时，引擎会想查找普通变量一样，沿着作用域向外查找，找到第一份非箭头
* 函数作用域中绑定的this值。
* 这个值在箭头函数定义是就确定了，并且永远不会改变
* */













